package br.com.felix.easyclearcase.ui;

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.DefaultListModel;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.ListCellRenderer;
import javax.swing.RowSorter;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import br.com.felix.clearcase.command.AddToSourceControl;
import br.com.felix.clearcase.command.Checkin;
import br.com.felix.clearcase.command.Checkout;
import br.com.felix.clearcase.command.CommandOutput;
import br.com.felix.clearcase.command.CommandTarget;
import br.com.felix.clearcase.command.FindCheckouts;
import br.com.felix.clearcase.command.ListCreatedAfterDate;
import br.com.felix.clearcase.command.ListFiles;
import br.com.felix.clearcase.command.ParametrizedCommand;
import br.com.felix.clearcase.command.Undocheckout;
import br.com.felix.commandline.MemoryLog;
import br.com.felix.easyclearcase.Comment;
import br.com.felix.easyclearcase.EasyClearCaseExplorerConfiguration;
import br.com.felix.easyclearcase.EasyClearCaseFile;
import br.com.felix.easyclearcase.EasyClearCaseFile.ClearCaseStatus;
import br.com.felix.easyclearcase.tasks.ClearCaseTask;
import br.com.felix.easyclearcase.tasks.DialogTask;
import br.com.felix.easyclearcase.tasks.InterfaceTask;
import br.com.felix.easyclearcase.tasks.SequencedTask;
import br.com.felix.easyclearcase.tasks.SystemCommandTask;
import br.com.felix.easyclearcase.tasks.Task;
import br.com.felix.easyclearcase.tasks.TaskCompletedStatusListener;
import br.com.felix.easyclearcase.tasks.TaskGroup;
import br.com.felix.easyclearcase.tasks.TaskStatusListener;
import br.com.felix.easyclearcase.tasks.Task.TaskStatus;
import br.com.felix.easyclearcase.ui.dialogs.AddToSourceControlDialog;
import br.com.felix.easyclearcase.ui.dialogs.CheckinDialog;
import br.com.felix.easyclearcase.ui.dialogs.EasyClearCaseExplorerConfigurationDialog;
import br.com.felix.easyclearcase.ui.dialogs.ExecuteCommandDialog;
import br.com.felix.easyclearcase.ui.dialogs.ExecutionReport;
import br.com.felix.easyclearcase.ui.dialogs.ListCreatedAfterDateDialog;
import br.com.felix.easyclearcase.ui.dialogs.NewCommandDialog;
import br.com.felix.easyclearcase.ui.dialogs.RemoveCommandDialog;
import br.com.felix.easyfileexplorer.EasyIcon;
import br.com.felix.easyfileexplorer.ui.EasyFileExplorer;
import br.com.felix.util.ILogDevice;
import br.com.felix.util.io.DirectoryFileFilter;

/**
 * This is the main interface class for the EasyClearCaseExplorer project.
 * Some code is embarassing. Refactors are needed.
 * */
public class EasyClearCaseExplorer {
	
	private JFrame jfrMain = new JFrame("Easy ClearCase Explorer - by Carlos Padoa");
	
	/**
	 * The window toolbar.
	 * */
	private JMenuBar menu = new JMenuBar();
	
	/**
	 * The current vob that is opened. May be null is none was opened.
	 * */
	private File vobRootDirectory;
	
	/**
	 * Panel where files are displayed.
	 * */
	private JPanel panelFiles = new JPanel();
	
	/**
	 * PopUpMenu that pops up when directories are right mouse clicked.
	 * */
	private JPopupMenu popUpMenuDirectories = new JPopupMenu();
	
	/**
	 * PopUpMenu that pops up when files are right mouse clicked.
	 * */
	private JPopupMenu popUpMenuFiles = new JPopupMenu();
	
	/**
	 * Shows the file system tree structure.
	 * */
	private JTree tree;
	
	/**
	 * Contains the list of files of the selected directory in the tree.
	 * */
	private JTable tableFiles; 
	
	/**
	 * Execute the program's operations in a threaded manner.
	 * */
	private ExecutorService taskExecutor = Executors.newFixedThreadPool(5);
	
	/**
	 * Displays the operations' logs.
	 **/
	private JList lstLog;
	
	/**
	 * Displays the executing tasks.
	 * */
	private JList lstTasks = new JList(new DefaultListModel());
	
	/**
	 * ScrollPane that will contain the main part of the program: the directories and files.
	 * */
	private JScrollPane jspMain = new JScrollPane(); 
	
	/**
	 * Will contain the jspMain and the monitoring components (log e tasks).
	 * */
	private JSplitPane jspMainAndMonitoring;
	
	/**
	 * Logger that displays the logged item in the interface.
	 * @see EasyClearCaseExplorerLog
	 * */
	private final EasyClearCaseExplorerLog generalLog = new EasyClearCaseExplorerLog();
	
	/**
	 * Which column of the table contains the File objects.
	 * */
	private int easyfileIndexInTable = 1;
	
	/**
	 * scrollpane for the log.
	 * */
	private JScrollPane jspLog;
	
	private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
	
	/**
	 * If multiple updates on the file status tasks are launched, it makes sense to execute
	 * only the last one.
	 * */
	private UpdateStatusAfterClearCaseOperationsTaskGroup lastUpdateStatusTask = null;
	
	/**
	 * @see EasyFileExplorer
	 * */
	private EasyFileExplorer easyFileExplorer = null;

	
	/**
	 * Constructor. Initializes the UI with no Vob or directory opened.
	 * */
	public EasyClearCaseExplorer() {
		jfrMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jfrMain.setSize(new Dimension(1024, 768));
		
		//Setup menu
		JMenu mnuVob = new JMenu("VOB");
		JMenuItem mniOpen = new JMenuItem("Open");
		mniOpen.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(MouseEvent e) {
				InterfaceTask itask = new InterfaceTask(){
					public void changeInterface(){
						if(cancelled){
							changeStatus(TaskStatus.CANCELLED);
							return;
						}
						openVob();
					}
				};
				itask.setName("Open VOB");
				executeTask(itask,true);
			}
		});
		mnuVob.add(mniOpen);
		
		JMenu mnuRecentVobs = new JMenu("Recent");
		EasyClearCaseExplorerConfiguration config = EasyClearCaseExplorerConfiguration.instance();
		Stack<File> stkRecent = config.getRecentVobs();
		if(stkRecent != null && stkRecent.size()>0){
			for(int i=stkRecent.size()-1;i>=0;i--){
				final File file = stkRecent.get(i);
				JMenuItem mni = new JMenuItem(file.getName());
				mni.addMouseListener(new MouseAdapter(){
					@Override
					public void mousePressed(MouseEvent e) {
						openVob(file);
					}
				});
				mnuRecentVobs.add(mni);
			}
			mnuVob.add(mnuRecentVobs);
		}
		
		
		JMenu mnuCommands = new JMenu("Commands");
		JMenuItem mniAddNew = new JMenuItem("Add new");
		mniAddNew.addMouseListener(new MouseAdapter(){
			@Override
			public void mousePressed(MouseEvent e) {
				addNewCommand();
			}
		});
		mnuCommands.add(mniAddNew);
		
		JMenuItem mniRemoveCommand = new JMenuItem("Remove command...");
		mniRemoveCommand.addMouseListener(new MouseAdapter(){
			@Override
			public void mousePressed(MouseEvent e) {
				removeCommand();
			}
		});
		mnuCommands.add(mniRemoveCommand);
		
		JMenu mnuWindow = configureMenuWindow();
		
		menu.add(mnuVob);
		menu.add(mnuCommands);
		menu.add(mnuWindow);
		jfrMain.setJMenuBar(menu);
		panelFiles.setLayout(new CardLayout());
		configurePopUpDirectories();
		configurePopUpFiles();
		
		//Setup panes
		JScrollPane jspTasks = configureTasksPane();
		JScrollPane jspLog = configureLogPane();
		JSplitPane jspTasksAndLog = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,jspTasks, jspLog);
		jspTasksAndLog.setDividerLocation(0.5d);
		jspMainAndMonitoring = new JSplitPane(JSplitPane.VERTICAL_SPLIT, jspMain,jspTasksAndLog);
		jspMainAndMonitoring.setDividerLocation(568);
		jfrMain.add(jspMainAndMonitoring);
	}



	protected void removeCommand() {
		RemoveCommandDialog rcd = new RemoveCommandDialog();
		rcd.show(true);
	}



	/**
	 * Configures the "Window" menu on the menubar.
	 * @return a JMenu with all the "Window" functionalities.
	 * */
	private JMenu configureMenuWindow() {
		JMenu mnuWindow = new JMenu("Window");
		JMenu mnuShowEasyFileExplorer = new JMenu("Show EasyFileExplorer");
		JMenuItem mniEasyFileExplorerHorizontally = new JMenuItem("Horizontally");
		mniEasyFileExplorerHorizontally.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				showEasyFileExplorer(true);
			}
		});
		JMenuItem mniEasyFileExplorerVertically = new JMenuItem("Vertically");
		mniEasyFileExplorerVertically.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				showEasyFileExplorer(false);
			}
		});
		
		JMenuItem mniHideEasyFileExplorer = new JMenuItem("Hide EasyFileExplorer");
		mniHideEasyFileExplorer.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				hideEasyFileExplorer();
			}
		});
		
		JMenuItem mniPreferences = new JMenuItem("Preferences");
		mniPreferences.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				EasyClearCaseExplorerConfigurationDialog eccecd = new EasyClearCaseExplorerConfigurationDialog();
				eccecd.setModal(true);
				eccecd.setVisible(true);
			}
		});
		
		mnuShowEasyFileExplorer.add(mniEasyFileExplorerHorizontally);
		mnuShowEasyFileExplorer.add(mniEasyFileExplorerVertically);
		mnuWindow.add(mnuShowEasyFileExplorer);
		mnuWindow.add(mniHideEasyFileExplorer);
		mnuWindow.add(mniPreferences);
		return mnuWindow;
	}



	/**
	 * Hides the EasyFileExplorer.
	 * @see EasyFileExplorer
	 * */
	protected void hideEasyFileExplorer() {
		jfrMain.getContentPane().removeAll();
		jfrMain.add(jspMainAndMonitoring);
		jfrMain.repaint();
		jfrMain.validate();
	}



	/**
	 * Shows the EasyFileExplorer.
	 * Will divide the screen horizontally or vertically.
	 * @param horizontal if true, the screen will be splitted horizontally. Vertically otherwise.
	 * */
	protected void showEasyFileExplorer(boolean horizontal) {
		jfrMain.getContentPane().removeAll();
		if(easyFileExplorer==null){
			easyFileExplorer = new EasyFileExplorer();
		}
		JSplitPane jspMainWithEasyFileExplorer = new JSplitPane(horizontal?JSplitPane.VERTICAL_SPLIT:JSplitPane.HORIZONTAL_SPLIT,easyFileExplorer,jspMainAndMonitoring);
		jspMainWithEasyFileExplorer.setDividerLocation(horizontal?200:400);
		jfrMain.add(jspMainWithEasyFileExplorer);
		jfrMain.repaint();
		jfrMain.validate();
	}



	/**
	 * Opens a NewCommandDialog.
	 * @see NewCommandDialog
	 * */
	private void addNewCommand() {
		final NewCommandDialog cmdDialog = new NewCommandDialog();
		cmdDialog.addSaveMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				ParametrizedCommand cmd = cmdDialog.getParametrizedCommand();
				EasyClearCaseExplorerConfiguration config = EasyClearCaseExplorerConfiguration.instance();
				config.addCommand(cmd);
				cmdDialog.dispose();
				JOptionPane.showMessageDialog(jfrMain, "Please re-start the application for the changes to take effect.");
			}
		});
		cmdDialog.setModal(true);
		cmdDialog.setVisible(true);
	}



	/**
	 * Opens a Vob: shows the directory tree structure.
	 * */
	private void openVob(File file) {
		
		vobRootDirectory = file;
		generalLog.log("Opening VOB '"+vobRootDirectory+"'...");
		try {
			initializeDirectoryTree();
			EasyClearCaseExplorerConfiguration config = EasyClearCaseExplorerConfiguration.instance();
			config.vobOpened(vobRootDirectory);
			generalLog.log("...VOB opened.");
		}
		catch (FileNotFoundException e) {
			log("Error accessing VOB. Maybe location is no longer valid.");
			generalLog.log("...VOB failed to open.");
		}
		
	}



	/**
	 * Configures the PopUpMenu that is displayed for directories.
	 * @see EasyClearCaseExplorer#popUpMenuDirectories
	 * */
	private void configurePopUpDirectories() {
		JMenu mnuRecursive = new JMenu("Recursively");
		JMenuItem mniCheckoutRecursively = new JMenuItem("Checkout", EasyIcon.LEFT.icon());
		mniCheckoutRecursively.addMouseListener(new EasyFileMouseAdapter(){
			@Override
			public void processFile(EasyClearCaseFile easyfile) {
				checkoutRecursively(easyfile);
			}
		});
		
		JMenuItem mniCheckinRecursively = new JMenuItem("Checkin", EasyIcon.RIGHT.icon());
		mniCheckinRecursively.addMouseListener(new EasyFileMouseAdapter(){
			@Override
			public void processFile(EasyClearCaseFile easyfile) {
				checkinRecursively(easyfile);
			}
		});
		
		JMenuItem mniUndocheckoutRecursively = new JMenuItem("Undo Checkout", EasyIcon.REDO.icon());
		mniUndocheckoutRecursively.addMouseListener(new EasyFileMouseAdapter(){
			@Override
			public void processFile(EasyClearCaseFile easyfile) {
				undocheckoutRecursively(easyfile);
			}
		});
		
		JMenuItem mniAddToSourceControlRecursively = new JMenuItem("Add to Source Control", EasyIcon.ADD.icon());
		mniAddToSourceControlRecursively.addMouseListener(new EasyFileMouseAdapter(){
			@Override
			public void processFile(EasyClearCaseFile easyfile) {
				addToSourceControlRecursively(easyfile);
			}
		});
		
		mnuRecursive.add(mniCheckinRecursively);
		mnuRecursive.add(mniCheckoutRecursively);
		mnuRecursive.add(mniUndocheckoutRecursively);
		mnuRecursive.add(mniAddToSourceControlRecursively);
		
		//NOT RECURSIVE -----------------------------------------------------
		
		JMenuItem mniCheckout = new JMenuItem("Checkout", EasyIcon.LEFT.icon());
		mniCheckout.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				if(node!=null){
					EasyClearCaseFile easyfile = (EasyClearCaseFile)node.getUserObject();
					UpdateTreeStatusAfterClearCaseOperationsTaskGroup tgroup = new UpdateTreeStatusAfterClearCaseOperationsTaskGroup(easyfile);
					checkout(easyfile, tgroup);
					tgroup.setName("Checkout task group");
					tgroup.startTasks();
				}
			}
		});
		JMenuItem mniCheckin = new JMenuItem("Checkin", EasyIcon.RIGHT.icon());
		mniCheckin.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				if(node!=null){
					EasyClearCaseFile easyfile = (EasyClearCaseFile)node.getUserObject();
					UpdateTreeStatusAfterClearCaseOperationsTaskGroup tgroup = new UpdateTreeStatusAfterClearCaseOperationsTaskGroup(easyfile);
					Comment comment = new Comment();
					checkin(easyfile,tgroup,comment);
					tgroup.setName("Checkin task group");
					tgroup.startTasks();
				}
			}
		});
		JMenuItem mniUndoCheckout = new JMenuItem("Undo Checkout", EasyIcon.REDO.icon());
		mniUndoCheckout.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				if(node!=null){
					EasyClearCaseFile easyfile = (EasyClearCaseFile)node.getUserObject();
					UpdateTreeStatusAfterClearCaseOperationsTaskGroup tgroup = new UpdateTreeStatusAfterClearCaseOperationsTaskGroup(easyfile);
					tgroup.displayFilesAfter(true);
					undoCheckout(easyfile, tgroup);
					tgroup.setName("Undo Checkout task group");
					tgroup.startTasks();
				}
				
			}
		});
		
		JMenuItem mniAddToSourceControl = new JMenuItem("Add to Source Control", EasyIcon.ADD.icon());
		mniAddToSourceControl.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				EasyClearCaseFile easyFolder = (EasyClearCaseFile)node.getUserObject();
				File parentFolder = easyFolder.getFile().getParentFile();
				EasyClearCaseFile easyParentFolder = EasyClearCaseFile.fromFile(parentFolder);
				UpdateTreeStatusAfterClearCaseOperationsTaskGroup tgroup = new UpdateTreeStatusAfterClearCaseOperationsTaskGroup(easyParentFolder);
				Comment commonComment = new Comment();
				commonComment.setComment(EasyClearCaseExplorerConfiguration.instance().getDefaultAddToSourceControlComment());
				addToSourceControl(easyFolder, tgroup, commonComment);
				tgroup.setName("Add to Source Control task group");
				tgroup.startTasks();
			}
		});
		
		
		JMenuItem mniFindCheckouts= new JMenuItem("Find Checkouts", EasyIcon.SEARCH.icon());
		mniFindCheckouts.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				EasyClearCaseFile easyFolder = (EasyClearCaseFile)node.getUserObject();
				findCheckouts(easyFolder);
			}
		});
		
		
		JMenuItem mniCreatedAfterDate = new JMenuItem("List files created since...", EasyIcon.SEARCH.icon());
		mniCreatedAfterDate.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				EasyClearCaseFile easyFolder = (EasyClearCaseFile)node.getUserObject();
				listFilesCreatedAfterDate(easyFolder);
			}
		});
		
		final JMenuItem mniPaste = new JMenuItem("Paste files", EasyIcon.PASTE.icon());
		mniPaste.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				EasyClearCaseFile easyFolder = (EasyClearCaseFile)node.getUserObject();
				pasteFiles(easyFolder);
			}
		});
		
		popUpMenuDirectories.addPopupMenuListener(new PopupMenuListener(){
			public void popupMenuCanceled(PopupMenuEvent e) {
			}
			public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
			}
			public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
				mniPaste.setEnabled(hasFilesToPaste());
			}
		});
		
		popUpMenuDirectories.add(mnuRecursive);
		popUpMenuDirectories.add(mniCheckout);
		popUpMenuDirectories.add(mniCheckin);
		popUpMenuDirectories.add(mniUndoCheckout);
		popUpMenuDirectories.add(mniAddToSourceControl);
		popUpMenuDirectories.add(mniFindCheckouts);
		popUpMenuDirectories.add(mniCreatedAfterDate);
		popUpMenuDirectories.add(mniPaste);
		
		
		EasyClearCaseExplorerConfiguration config = EasyClearCaseExplorerConfiguration.instance();
		List<ParametrizedCommand> lstCommands = config.getParametrizedCommandsList();
		int directoriesCommandCount = 0;
		if(lstCommands!=null){
			JMenu mnuMyCommands = new JMenu("My commands");
			for(final ParametrizedCommand cmd: lstCommands){
				if(CommandTarget.DIRECTORIES.equals(cmd.getTarget())||CommandTarget.FILES_AND_DIRECTORIES.equals(cmd.getTarget())){
					JMenuItem mniCustomCommand = new JMenuItem(cmd.getName());
					mniCustomCommand.addMouseListener(new MouseAdapter(){
						@Override
						public void mouseReleased(MouseEvent e) {
							DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
							EasyClearCaseFile easyfile = (EasyClearCaseFile)node.getUserObject();
							UpdateTreeStatusAfterClearCaseOperationsTaskGroup tgroup = new UpdateTreeStatusAfterClearCaseOperationsTaskGroup(easyfile);
							executeParametrizedCommand(easyfile,cmd,tgroup);
							tgroup.setName(cmd.getName() + " task group");
							tgroup.startTasks();
						}
					});
					mnuMyCommands.add(mniCustomCommand);
					directoriesCommandCount++;
				}
			}
			if(directoriesCommandCount>0)popUpMenuDirectories.add(mnuMyCommands);
		}
		
	}
	
	
	
	protected void findCheckouts(EasyClearCaseFile easyFolder) {
		FindCheckouts findcheckoutscommand = new FindCheckouts(easyFolder);
		ClearCaseTask cct = new ClearCaseTask(findcheckoutscommand);
		cct.setName("Find checkouts "+easyFolder.getFile().getName());
		final MemoryLog mlog = new MemoryLog();
		EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
		cct.setLogger(eccel);
		cct.setOutputLogger(mlog);
		cct.addTaskStatusListener(new TaskCompletedStatusListener(){
			@Override
			public void doAfterTask(TaskStatus e) {
				listFilesFromClearToolOutput(mlog.toString());
			}
		});
		executeTask(cct, true);
	}



	/**
	 * Starts a taskgroup to Add files to source control recursively.
	 * */
	protected void addToSourceControlRecursively(EasyClearCaseFile file) {
		UpdateStatusAfterClearCaseOperationsSequencedTaskGroup sequencedTaskGroup = new UpdateStatusAfterClearCaseOperationsSequencedTaskGroup(file);
		sequencedTaskGroup.setName("Add to Source Control recursively");
		Comment commonComment = new Comment();
		commonComment.setComment(EasyClearCaseExplorerConfiguration.instance().getDefaultAddToSourceControlComment());
		includeFilesForAddToSourceControl(file.getFile(), sequencedTaskGroup, commonComment);		
		System.out.println(sequencedTaskGroup.toString());
		sequencedTaskGroup.setGenerateReport(true);
		sequencedTaskGroup.startTasks();
	}



	/**
	 * Helper method to recurse the directories' structure and create an AddToSourceControl task for each file/folder.
	 * */
	private void includeFilesForAddToSourceControl(File file, TaskGroup sequencedTaskGroup, Comment commonComment) {
		if(file.isDirectory()){
			addToSourceControl(EasyClearCaseFile.fromFile(file), sequencedTaskGroup, commonComment);
			File[] files = file.listFiles();
			for(File f: files){
				includeFilesForAddToSourceControl(f, sequencedTaskGroup, commonComment);
			}
		}
		else{
			addToSourceControl(EasyClearCaseFile.fromFile(file), sequencedTaskGroup, commonComment);
		}
	}



	/**
	 * Starts a taskgroup to undocheckout files recursively.
	 * */
	protected void undocheckoutRecursively(EasyClearCaseFile file) {
		UpdateStatusAfterClearCaseOperationsSequencedTaskGroup sequencedTaskGroup = new UpdateStatusAfterClearCaseOperationsSequencedTaskGroup(file);
		sequencedTaskGroup.setName("Undo Checkout recursively");
		sequencedTaskGroup.setGenerateReport(true);
		includeFilesForUndoCheckout(file.getFile(), sequencedTaskGroup);		
		sequencedTaskGroup.startTasks();
	}



	/**
	 * Helper method to recurse the directories' structure and create an UndoCheckout task for each file/folder.
	 * */
	private void includeFilesForUndoCheckout(File file, TaskGroup sequencedTaskGroup) {
		if(file.isDirectory()){
			undoCheckout(EasyClearCaseFile.fromFile(file), sequencedTaskGroup);
			File[] files = file.listFiles();
			for(File f: files){
				includeFilesForUndoCheckout(f, sequencedTaskGroup);
			}
		}
		else{
			undoCheckout(EasyClearCaseFile.fromFile(file), sequencedTaskGroup);
		}
	}



	/**
	 * Checks the system clipbord to see if there is any files in it.
	 * @return true if there are any files in system's clipboard.
	 * */
	private boolean hasFilesToPaste(){
		Clipboard clipboard =  Toolkit.getDefaultToolkit().getSystemClipboard();
		Transferable contents = clipboard.getContents(null);
	    boolean hasTransferableText = (contents != null) && contents.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
	    return hasTransferableText;
	}


	/**
	 * Execute system commands to copy the clipboard's files and folder into the specified folder.
	 * */
	public void pasteFiles(EasyClearCaseFile easyFolder) {
		Clipboard clipboard =  Toolkit.getDefaultToolkit().getSystemClipboard();
		Transferable contents = clipboard.getContents(null);
	    boolean hasTransferableText = (contents != null) && contents.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
	    if (hasTransferableText) {
	      try {
	        List<File> result = (List<File>)contents.getTransferData(DataFlavor.javaFileListFlavor);
	        UpdateStatusAfterClearCaseOperationsTaskGroup tgroup = new UpdateStatusAfterClearCaseOperationsTaskGroup(easyFolder);
	        tgroup.setName("Copy files task group");
	        String command;
	        SystemCommandTask systemCommandTask;
	        EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
	        for(File f:result){
	        	if(f.isDirectory()){
	        		command = "cmd.exe /c md \""+ easyFolder.getAbsolutePath()+"\\"+f.getName()+"\" & xcopy \""+f.getAbsolutePath()+"\" \""+easyFolder.getAbsolutePath()+"\\"+f.getName()+"\" /E";
	        	}
	        	else{
	        		command = "cmd.exe /c copy \""+f.getAbsolutePath()+"\" \""+easyFolder.getAbsolutePath()+"\"";	
	        	}
	        	systemCommandTask = new SystemCommandTask(command);
	        	systemCommandTask.setName("Copy file "+f.getName()+" to "+easyFolder.getName());
	        	systemCommandTask.setOutputLog(eccel);
	        	systemCommandTask.setErrorLogger(eccel);
				tgroup.add(systemCommandTask);
	        }
	        tgroup.startTasks();
	      }
	      catch (UnsupportedFlavorException ex){
	        //highly unlikely since we are using a standard DataFlavor
	        System.out.println(ex);
	        ex.printStackTrace();
	      }
	      catch (IOException ex) {
	        System.out.println(ex);
	        ex.printStackTrace();
	      }
	    }
	} 


		


	/**
	 * Adds a DialogTask to the specified taskgroup. 
	 * This task will gather the parameters and execute the parametrized task.
	 * 
	 * */
	private void executeParametrizedCommand(EasyClearCaseFile easyfile, ParametrizedCommand cmd, TaskGroup tgroup) {
		cmd.setFile(easyfile);
		final ExecuteCommandDialog dialog = new ExecuteCommandDialog(cmd);
		DialogTask dt = new DialogTask(dialog);
		dt.setName("User input task");
		final ClearCaseTask cct = new ClearCaseTask(cmd);
		EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
		cct.setLogger(eccel);
		if(cmd.getOutput().equals(CommandOutput.FILE_LIST)){
			final MemoryLog memoryLog = new MemoryLog();
			cct.setOutputLogger(memoryLog);
			cct.addTaskStatusListener(new TaskCompletedStatusListener(){
				@Override
				public void doAfterTask(TaskStatus e) {
					listFilesFromClearToolOutput(memoryLog.toString());
				}
			});
		}
		cct.setName(cmd.getName()+" -> "+easyfile.getName());
		dialog.addOkButtonMouseListener(new MouseAdapter(){
			@Override
			public void mouseReleased(MouseEvent e) {
				dialog.dispose();
				executeTask(cct, true);
			}
		});
		tgroup.add(dt);
	}




	/**
	 * Configures the PopUpMenu that is displayed for files.
	 * @see EasyClearCaseExplorer#popUpMenuFiles
	 * */
	private void configurePopUpFiles() {
		JMenuItem mniCheckout = new JMenuItem("Checkout",EasyIcon.LEFT.icon());
		mniCheckout.addMouseListener(new ClearCaseMouseAdapter("Checkout task group"){
			@Override
			public void doClearCaseTask(EasyClearCaseFile file, TaskGroup tgroup,Comment commonComment) {
				checkout(file, tgroup);
			}
		});
		
		JMenuItem mniCheckin = new JMenuItem("Checkin",EasyIcon.RIGHT.icon());
		mniCheckin.addMouseListener(new ClearCaseMouseAdapter("Checkin task group"){
			@Override
			public void doClearCaseTask(EasyClearCaseFile file, TaskGroup tgroup,Comment commonComment) {
				checkin(file, tgroup, commonComment);
			}
		});
		
		JMenuItem mniUndoCheckout = new JMenuItem("Undo Checkout",EasyIcon.REDO.icon());
		mniUndoCheckout.addMouseListener(new ClearCaseMouseAdapter("Undo Checkout task group"){
			@Override
			public void doClearCaseTask(EasyClearCaseFile file, TaskGroup tgroup,Comment commonComment) {
				undoCheckout(file, tgroup);
			}
		});
		
		JMenuItem mniAddToSourceControl = new JMenuItem("Add to Source Control",EasyIcon.ADD.icon());
		mniAddToSourceControl.addMouseListener(new ClearCaseMouseAdapter("Add to Source Control task group"){

			@Override
			public void doClearCaseTask(EasyClearCaseFile file, TaskGroup tgroup, Comment commonComment) {
				commonComment.setComment(EasyClearCaseExplorerConfiguration.instance().getDefaultAddToSourceControlComment());
				addToSourceControl(file, tgroup, commonComment);
				
			}
		});
		popUpMenuFiles.add(mniCheckout);
		popUpMenuFiles.add(mniCheckin);
		popUpMenuFiles.add(mniUndoCheckout);
		popUpMenuFiles.add(mniAddToSourceControl);
		
		EasyClearCaseExplorerConfiguration config = EasyClearCaseExplorerConfiguration.instance();
		List<ParametrizedCommand> lstCommands = config.getParametrizedCommandsList();
		int fileCommandsCount = 0;
		if (lstCommands != null) {
			JMenu mnuMyCommands = new JMenu("My commands");
			for (final ParametrizedCommand cmd : lstCommands) {
				if (CommandTarget.FILES.equals(cmd.getTarget()) || CommandTarget.FILES_AND_DIRECTORIES.equals(cmd.getTarget())) {
					JMenuItem mniCustomCommand = new JMenuItem(cmd.getName());
					mniCustomCommand.addMouseListener(new MouseAdapter() {

						@Override
						public void mouseReleased(MouseEvent e) {
							int rows[] = tableFiles.getSelectedRows();
							DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
							EasyClearCaseFile easyParentfile = (EasyClearCaseFile) node.getUserObject();
							UpdateStatusAfterClearCaseOperationsTaskGroup tgroup = new UpdateStatusAfterClearCaseOperationsTaskGroup(easyParentfile);
							for (int row : rows) {
								EasyClearCaseFile easyfile = (EasyClearCaseFile) tableFiles.getModel().getValueAt(row, easyfileIndexInTable);
								if (easyfile != null) {
									executeParametrizedCommand(easyfile, cmd, tgroup);
								}
							}
							tgroup.setName(cmd.getName() + " task group");
							tgroup.startTasks();
						}
					});
					mnuMyCommands.add(mniCustomCommand);
					fileCommandsCount++;
				}
			}
			if(fileCommandsCount>0)popUpMenuFiles.add(mnuMyCommands);
		}
	}
	


	/**
	 * Adds an AddToSourceControlTask to the specified task group.
	 * @param file the file to be added to source control.
	 * @param tgroup the taskgroup that will hold the necessary tasks.
	 * @param commonComment the Comment reference to be shared among multiple addToSourceControl tasks.
	 * */
	protected void addToSourceControl(final EasyClearCaseFile file,final TaskGroup tgroup,final Comment commonComment) {
		
		Task addToSourceControlCommentTask = new SequencedTask(){
			@Override
			public void doRun() {
				if(cancelled){
					changeStatus(TaskStatus.CANCELLED);
					return;
				}
				EasyClearCaseExplorerConfiguration config = EasyClearCaseExplorerConfiguration.instance();
				Boolean addToSourceControlAsksForUserComment = config.getAddToSourceControlAsksForUserComment();
				if(addToSourceControlAsksForUserComment == null || addToSourceControlAsksForUserComment.booleanValue()){
					changeStatus(TaskStatus.IN_PROGRESS);
					if(!commonComment.isApplyToAll()){ 
						final AddToSourceControlDialog atscDialog = new AddToSourceControlDialog(jfrMain,file,commonComment);
						atscDialog.addCommitMouseListener(new MouseAdapter(){
							@Override
							public void mouseReleased(MouseEvent e) {
								if(atscDialog.getApplyToAll()){
									commonComment.setApplyToAll(true);
									commonComment.setComment(atscDialog.getComment());
								}
								String comment = atscDialog.getComment();
								addToSourceControl(file, comment);
							}
						});
						atscDialog.addSkipMouseListener(new MouseAdapter(){
							@Override
							public void mouseReleased(MouseEvent e) {
								changeStatus(TaskStatus.CANCELLED);
							}
						});
						atscDialog.addCancelMouseListener(new MouseAdapter(){
							@Override
							public void mouseReleased(MouseEvent e) {
								tgroup.cancel();
								changeStatus(TaskStatus.CANCELLED);
							}
						});
						atscDialog.setModal(false);
						atscDialog.setVisible(true);
					}
					else{
						String comment = commonComment.getComment();
						addToSourceControl(file, comment);
					}
				}
				else{
					String comment = config.getDefaultAddToSourceControlComment();
					addToSourceControl(file, comment);
				}
			}

			private void addToSourceControl(final EasyClearCaseFile file, String comment) {
				EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
				EasyClearCaseFile parentEasyClearCaseFile = file.getParentEasyClearCaseFile();
				ClearCaseTask cctParent = new ClearCaseTask(new Checkout(parentEasyClearCaseFile));
				cctParent.setName("Checkout parent folder.");
				cctParent.setLogger(eccel);

				AddToSourceControl addToSourceControlCommand = new AddToSourceControl(file,comment);
				final ClearCaseTask cct = new ClearCaseTask(addToSourceControlCommand);
				cct.addTaskStatusListener(new TaskCompletedStatusListener(){
					@Override
					public void doAfterTask(TaskStatus e) {
						changeStatus(TaskStatus.COMPLETE_SUCCESS);
					}
				});
				cct.setName("Add to Source Control "+file.getName());
				cct.setLogger(eccel);
				
				cctParent.addTaskStatusListener(new TaskCompletedStatusListener(){
					@Override
					public void doAfterTask(TaskStatus e) {
						executeTask(cct,true);
					}
				});
				executeTask(cctParent,true);

			}
			
			
			public String toString(){
				return "Add to source control file " + file.getName();
			}

			@Override
			public boolean singleThreaded() {
				return false;
			}
		};
		addToSourceControlCommentTask.setName("Add To Source Control comment dialog");
		tgroup.add(addToSourceControlCommentTask);
	}




	/**
	 * Adds an UndocheckoutTask to the specified task group.
	 * @param file the file that will be undocheckedout
	 * @param tgroup the taskgroup that will hold the necessary tasks.
	 * */
	protected void undoCheckout(EasyClearCaseFile file, TaskGroup tgroup) {
		EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
		Undocheckout undocheckoutcommand = new Undocheckout(file);
		ClearCaseTask cct = new ClearCaseTask(undocheckoutcommand);
		cct.setName("Undocheckout "+file.getName());
		cct.setLogger(eccel);
		tgroup.add(cct);
	}


	/**
	 * Adds a checkin task to the specified task group.
	 * @param file the file that will be checked in.
	 * @param tgroup the taskgroup that will hold the necessary tasks.
 	 * @param commonComment the Comment reference to be shared among multiple checkin tasks.
	 * */
	protected void checkin(final EasyClearCaseFile file, final TaskGroup tgroup, final Comment commonComment) {
		Task checkinCommentTask = new SequencedTask(){
			@Override
			public void doRun() {
				if(cancelled){
					changeStatus(TaskStatus.CANCELLED);
					return;
				}
				changeStatus(TaskStatus.IN_PROGRESS);
				if(!commonComment.isApplyToAll()){ 
					final CheckinDialog cd = new CheckinDialog(jfrMain,file,commonComment);
					cd.addCommitMouseListener(new MouseAdapter(){
						@Override
						public void mouseReleased(MouseEvent e) {
							EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
							String comment = cd.getComment();
							Checkin checkincommand = new Checkin(file,comment);
							ClearCaseTask cct = new ClearCaseTask(checkincommand);
							cct.setName("Checkin "+file.getName());
							cct.setLogger(eccel);
							executeTask(cct,true);
						}
					});
					cd.addSkipMouseListener(new MouseAdapter(){
						@Override
						public void mouseReleased(MouseEvent e) {
							changeStatus(TaskStatus.CANCELLED);
						}
					});
					cd.addCancelMouseListener(new MouseAdapter(){
						@Override
						public void mouseReleased(MouseEvent e) {
							tgroup.cancel();
							changeStatus(TaskStatus.CANCELLED);
						}
					});
					cd.setModal(true);
					cd.setVisible(true);
				}
				else{
					EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
					String comment = commonComment.getComment();
					Checkin checkincommand = new Checkin(file,comment);
					ClearCaseTask cct = new ClearCaseTask(checkincommand);
					cct.setName("Checkin "+file.getName());
					cct.setLogger(eccel);
					executeTask(cct,true);
				}
				changeStatus(TaskStatus.COMPLETE_SUCCESS);
			}

			@Override
			public boolean singleThreaded() {
				return false;
			}
		};
		checkinCommentTask.setName("Checkin comment dialog");
		tgroup.add(checkinCommentTask);
	}


	/**
	 * Adds a checkout task to the specified task group.
	 * @param file the file that will be checked out.
	 * @param tgroup the taskgroup that will hold the necessary tasks.
	 * */
	protected void checkout(EasyClearCaseFile file, TaskGroup tgroup) {
		EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
		Checkout checkoutcommand = new Checkout(file);
		ClearCaseTask cct = new ClearCaseTask(checkoutcommand);
		cct.setName("Checkout "+file.getName());
		cct.setLogger(eccel);
		tgroup.add(cct);
	}


	/**
	 * Displays the task in the task list (if specified to do so), and schedules its execution.
	 * */
	private void executeTask(final Task task, boolean display) {
		if(display){
			DefaultListModel dm = (DefaultListModel)lstTasks.getModel();
			dm.insertElementAt(task, 0);
			task.addTaskStatusListener(new TaskStatusDisplayListener());
		}
		taskExecutor.execute(task);
	}
	


	/**
	 * Start lots of checkin tasks.
	 * @param file the folder whose files and folders will be recursively checked in.
	 * */
	protected void checkinRecursively(final EasyClearCaseFile file) {
		UpdateStatusAfterClearCaseOperationsSequencedTaskGroup sequencedTaskGroup = new UpdateStatusAfterClearCaseOperationsSequencedTaskGroup(file);
		sequencedTaskGroup.setName("Checkin recursively");
		sequencedTaskGroup.setGenerateReport(true);
		Comment comment = new Comment();
		includeFilesForCheckin(file.getFile(), sequencedTaskGroup, comment);
		sequencedTaskGroup.setGenerateReport(true);
		sequencedTaskGroup.startTasks();
	}

	
	/**
	 * Starts a dialog to gather user input, then run the approppriate task.
	 * @see ListCreatedAfterDate
	 * @see ListCreatedAfterDateDialog
	 * */
	protected void listFilesCreatedAfterDate(final EasyClearCaseFile easyFolder) {
		final ListCreatedAfterDateDialog lcadd = new ListCreatedAfterDateDialog(jfrMain);
		final MemoryLog outputLogger = new MemoryLog();
		lcadd.addCommitMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(MouseEvent e) {
				Date data = lcadd.getDate();
				ListCreatedAfterDate listaftercreateddatecommand = new ListCreatedAfterDate(easyFolder, data);
				ClearCaseTask cct = new ClearCaseTask(listaftercreateddatecommand);
				cct.setName("List files from " + easyFolder.getName() + " created since specified date.");
				EasyClearCaseExplorerLog eccel = new EasyClearCaseExplorerLog();
				cct.setLogger(eccel);
				cct.setOutputLogger(outputLogger);
				cct.addTaskStatusListener(new TaskCompletedStatusListener(){
					@Override
					public void doAfterTask(TaskStatus e) {
						if(e == TaskStatus.COMPLETE_SUCCESS){
							listFilesFromClearToolOutput(outputLogger.toString());
						}
					}
				});
				executeTask(cct,true);
			}
		});
		DialogTask dialogtask = new DialogTask(lcadd);			
		dialogtask.setName("List files date input dialog");
		executeTask(dialogtask,true);
	}
	
	
	/**
	 * This method parses cleartool outputs that are in the "extended format" and
	 * display the list of files in the main window.
	 * @param clearToolOutput the output result of a cleartool command. Must be in the extended format.
	 * */
	private void listFilesFromClearToolOutput(String clearToolOutput) {
		System.out.println("Cleartool output");
		System.out.println(clearToolOutput);
		Pattern empty = Pattern.compile("\\W*");
		Matcher m = empty.matcher(clearToolOutput);
		String[] lines = null;
		if(m.matches()){
			generalLog.log("ClearTool output has no files.");
			lines = new String[0];
		}
		else{
			lines = clearToolOutput.trim().split("\n");
		}
		EasyClearCaseFile [] easyFiles = new EasyClearCaseFile[lines.length];
		String filename;
		ClearCaseStatus ccstatus;
		for(int i=0; i<lines.length;i++){
			filename = parseFileNameFormShortDescription(lines[i]);
			ccstatus = parseStatusFromShortDescription(lines[i]);
			easyFiles[i]= EasyClearCaseFile.fromFile(new File(filename));
			easyFiles[i].setStatus(ccstatus);
		}
		displayFiles(easyFiles,true);
	}
	

	/**
	 * Recursive method to create a list of files for checkin.
	 * @param tgroup taskgroup where tasks will be added.
	 * @param commonComment comment reference that will be shared among the multiple tasks.
	 * @param file is the file that will be checked in.
	 * */
	private void includeFilesForCheckin(File file, TaskGroup sequencedTaskGroup, Comment commonComment) {
		if(file.isDirectory()){
			checkin(EasyClearCaseFile.fromFile(file), sequencedTaskGroup, commonComment);
			File[] files = file.listFiles();
			for(File f: files){
				includeFilesForCheckin(f, sequencedTaskGroup, commonComment);
			}
		}
		else{
			checkin(EasyClearCaseFile.fromFile(file), sequencedTaskGroup, commonComment);
		}
	}




	/**
	 * Start lots of checkout tasks.
	 * @param file the folder whose files and folders will be recursively checked out.
	 * */
	protected void checkoutRecursively(final EasyClearCaseFile file) {
		UpdateStatusAfterClearCaseOperationsSequencedTaskGroup sequencedTaskGroup = new UpdateStatusAfterClearCaseOperationsSequencedTaskGroup(file);
		sequencedTaskGroup.setName("Checkout recursively");
		sequencedTaskGroup.setGenerateReport(true);
		includeFilesForCheckout(file.getFile(), sequencedTaskGroup);		
		sequencedTaskGroup.startTasks();
	}


	/**
	 * Recursive method to create a list of files for checkout.
	 * */
	private void includeFilesForCheckout(File file, TaskGroup sequencedTaskGroup) {
		if(file.isDirectory()){
			checkout(EasyClearCaseFile.fromFile(file), sequencedTaskGroup);
			File[] files = file.listFiles();
			for(File f: files){
				includeFilesForCheckout(f, sequencedTaskGroup);
			}
		}
		else{
			checkout(EasyClearCaseFile.fromFile(file), sequencedTaskGroup);
		}
	}




	/**
	 * Displays the File Chooser dialog to open a VOB.
	 * */
	protected synchronized void openVob() {
		JFileChooser jfc = new JFileChooser();
		jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		int returnVal = jfc.showOpenDialog(this.jfrMain);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			openVob(jfc.getSelectedFile());
		}
	}
	

	/**
	 * Configures the tree interface component and the main part of the window.
	 * @throws FileNotFoundException 
	 * */
	private void initializeDirectoryTree() throws FileNotFoundException {

		JTree directoriesTree = initializeDirectoriesTreeComponent();
		JScrollPane jspDirectories = new JScrollPane(directoriesTree);
		JScrollPane jspFiles = new JScrollPane(panelFiles);
		JSplitPane jspDirectoriesAndFiles = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, jspDirectories, jspFiles);
		jspDirectoriesAndFiles.setDividerLocation(200);
		jspMain.setViewportView(jspDirectoriesAndFiles);
		jfrMain.validate();
	}
	

	private void log(String string) {
		JOptionPane.showMessageDialog(jfrMain, string,"Attention!",JOptionPane.WARNING_MESSAGE);
	}



	/**
	 * Configures the component that will display the logs.
	 * */
	private JScrollPane configureLogPane() {
		DefaultListModel dlm = new DefaultListModel();
		dlm.clear();
		lstLog = new JList(dlm);
		lstLog.setLayoutOrientation(JList.VERTICAL);
		jspLog = new JScrollPane(lstLog);
		jspLog.setAutoscrolls(true);
		return jspLog;
	}


	/**
	 * Configures the component that will display the tasks.
	 * */
	private JScrollPane configureTasksPane() {
		DefaultListModel dlm = (DefaultListModel)lstTasks.getModel();
		dlm.clear();
		lstTasks.setLayoutOrientation(JList.VERTICAL);
		lstTasks.setCellRenderer(new TaskCellRenderer());
		JScrollPane jspTasks = new JScrollPane(lstTasks);
		jspTasks.setAutoscrolls(true);
		return jspTasks;
	}



	/**
	 * Configures the tree component.
	 * @throws FileNotFoundException 
	 * */
	private JTree initializeDirectoriesTreeComponent() throws FileNotFoundException {
		DefaultMutableTreeNode top = new DefaultMutableTreeNode(EasyClearCaseFile.fromFile(vobRootDirectory));
		createNodes(top, vobRootDirectory, new DirectoryFileFilter());
		tree = new JTree(top);
		DefaultTreeCellRenderer dtcr = new DefaultTreeCellRenderer();
		dtcr.setLeafIcon(EasyIcon.FOLDER.icon());
		dtcr.setClosedIcon(EasyIcon.FOLDER.icon());
		dtcr.setOpenIcon(EasyIcon.FOLDER.icon());
		tree.setCellRenderer(dtcr);
		
		tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		tree.addTreeSelectionListener(new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent e) {
				final DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
				if (node == null) return;
				InterfaceTask itask = new InterfaceTask(){
					public void changeInterface(){
						if(cancelled){
							changeStatus(TaskStatus.CANCELLED);
							return;
						}
						displayFiles(((EasyClearCaseFile) node.getUserObject()));
					}
				};
				itask.setName("Display files");
				executeTask(itask,true);
			}
		});
		
		tree.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON3 && tree.getLastSelectedPathComponent() != null) {
					showPopUpDirectories(e);
				}
			}
		});
		return tree;
	}
	
	
	/**
	 * Adds nodes to the tree recursively. Directories only.
	 * @throws FileNotFoundException 
	 * */
	private void createNodes(DefaultMutableTreeNode top, File file, DirectoryFileFilter filter) throws FileNotFoundException {
		top.removeAllChildren();
		File[] directories = file.listFiles(filter);
		DefaultMutableTreeNode child;
		if(directories==null)throw new FileNotFoundException("Directory is not valid!");
		for (File f : directories) {
			child = new DefaultMutableTreeNode(EasyClearCaseFile.fromFile(f));
			top.add(child);
			createNodes(child, f, filter);
		}
	}

	
	
	/**
	 * Shows the popUp for directories at the current mouse location, if the user has clicked over a selected directory.
	 * */
	protected void showPopUpDirectories(MouseEvent e) {
		TreePath tpath = tree.getClosestPathForLocation(e.getX(), e.getY());
		DefaultMutableTreeNode mousenode = (DefaultMutableTreeNode) tpath.getLastPathComponent();
		DefaultMutableTreeNode selectednode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
		if(selectednode!=null && selectednode.equals(mousenode)){
			popUpMenuDirectories.show(tree, e.getX(), e.getY());
		}
	}
	
	
	
	/**
	 * Shows the popUp for files at the current mouse location, if the user has clicked over a selected file.
	 * */
	protected void showPopUpFiles(MouseEvent e) {
		int pointRow = tableFiles.rowAtPoint(e.getPoint());
		int rows[] = tableFiles.getSelectedRows();
		for(int row:rows){
			if(row == pointRow){
				popUpMenuFiles.show(tableFiles, e.getX(), e.getY());
				break;
			}
		}
	}
	
	
	
	/**
	 * Displays the files of the specified folder into the main panel and shows its clearcase statuses.
	 * */
	protected synchronized void displayFiles(final EasyClearCaseFile file) {
		EasyClearCaseFile[] files = file.listFiles();
		final Object[][] rowdata = displayFiles(files,false);
		final MemoryLog memoryLog = new MemoryLog();
		ListFiles lfDirectory = new ListFiles(file.getFile());
		lfDirectory.setDirectoryOnly(true);
		ClearCaseTask cctUpdateFolder = new ClearCaseTask(lfDirectory);
		cctUpdateFolder.setName("Update directory's status ("+file.getName()+")");
		cctUpdateFolder.setLogger(generalLog);
		final MemoryLog dirMLog = new MemoryLog();
		cctUpdateFolder.setOutputLogger(dirMLog);
		cctUpdateFolder.addTaskStatusListener(new TaskCompletedStatusListener(){
			@Override
			public void doAfterTask(TaskStatus e) {
				HashMap<String, ClearCaseStatus> mapStatus = createStatusMap(dirMLog);
				DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
				ClearCaseStatus status = mapStatus.get(".");
				EasyClearCaseFile easyfile = (EasyClearCaseFile)node.getUserObject();
				easyfile.setStatus(status);
				tree.repaint();
				tree.validate();
			}
		});
		
		ClearCaseTask cct = new ClearCaseTask(new ListFiles(file.getFile()));
		cct.setName("Update files' status (children of "+file.getName()+")");
		cct.setLogger(generalLog);
		cct.setOutputLogger(memoryLog);
		cct.addTaskStatusListener(new TaskCompletedStatusListener(){
			@Override
			public void doAfterTask(TaskStatus e) {
				HashMap<String, ClearCaseStatus> mapStatus = createStatusMap(memoryLog);
				for(int i=0; i< rowdata.length;i++){
					ClearCaseStatus status = mapStatus.get(((EasyClearCaseFile)rowdata[i][easyfileIndexInTable]).getName());
					rowdata[i][0] = status;
					EasyClearCaseFile easyfile = (EasyClearCaseFile) rowdata[i][easyfileIndexInTable];
					easyfile.setStatus(status);
				}
				file.setStatus(mapStatus.get(file.getName()));
				DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
				updateDirectoryTree(node, mapStatus);
				
				tableFiles.repaint();
				tableFiles.validate();
			}
		});
		executeTask(cctUpdateFolder, true);
		executeTask(cct, true);
	}
	

	/**
	 * Parses cleartool output.
	 * @return a map of files and its statuses.
	 * */
	private HashMap<String, ClearCaseStatus> createStatusMap(MemoryLog memoryLog) {
		String processOutput = memoryLog.toString();
		String []outputLines = processOutput.split("\n");
		String fileName;
		ClearCaseStatus ccstatus;
		HashMap<String,ClearCaseStatus> mapStatus = new HashMap<String,ClearCaseStatus>();
		for(String line:outputLines){
			fileName = parseFileNameFormShortDescription(line);
			ccstatus = parseStatusFromShortDescription(line);
			mapStatus.put(fileName, ccstatus);
		}
		return mapStatus;
	}
	
	

	/**
	 * Displays the specified files into the main panel.
	 * */
	protected synchronized Object[][] displayFiles(EasyClearCaseFile files[], boolean showCompletePath) {
		generalLog.log("Listing files...");
		final Object[][] rowdata = new Object[files.length][5];
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date lastModified;
		for (int i = 0; i < files.length; i++) {
			files[i].setRenderAbsolutePath(showCompletePath);
			String name = files[i].getName();
			rowdata[i][0] = files[i].getStatus();
			rowdata[i][1] = files[i];
			rowdata[i][2] = files[i].getFile().isDirectory()?"directory":name.substring(name.lastIndexOf('.') + 1).toUpperCase();
			lastModified = new Date(files[i].lastModified());
			rowdata[i][3] = sdf.format(lastModified);
			long length = files[i].length();
			long kilobytes = length!=0?length/1024:0;
			if(kilobytes!=0){
				rowdata[i][4] = String.valueOf(kilobytes)+" KB";
			}
			else{
				rowdata[i][4] = String.valueOf(length)+" bytes";
			}
		}
		String columnNames[] = {"Status", "File", "Type", "Date", "Size" };
		int columnSizes[] = {50,400,70,70,100};
		tableFiles = new JTable(rowdata, columnNames);
		for(int i=0; i<columnSizes.length;i++){
			tableFiles.getColumnModel().getColumn(i).setPreferredWidth(columnSizes[i]);
		}
		tableFiles.setGridColor(new Color(255,255,255));
		tableFiles.addMouseListener(new MouseAdapter(){
			@Override
			public void mousePressed(MouseEvent e) {
				if(e.getClickCount()>=2){
					tableFiles.editingCanceled(new ChangeEvent(e));
				}
				if (e.getButton() == MouseEvent.BUTTON3 && tree.getLastSelectedPathComponent() != null) {
					showPopUpFiles(e);
				}
			}
		});
		tableFiles.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
		tableFiles.setFillsViewportHeight(true);
		tableFiles.setAutoCreateRowSorter(true);
		JScrollPane jspTable = new JScrollPane(tableFiles);
		panelFiles.removeAll();
		panelFiles.add(jspTable, "MAIN");
		panelFiles.validate();
		generalLog.log("... listing completed.");
		return rowdata;
	}


	private void generateReport(List<Task> tasks){
		ExecutionReport er = new ExecutionReport(tasks);
		er.show();
	}
	

	public static void main(String args[]) {
		try {
		    // Set System L&F
	        UIManager.setLookAndFeel(
	            UIManager.getSystemLookAndFeelClassName());
	    } 
	    catch (UnsupportedLookAndFeelException e) {
	       e.printStackTrace();
	    }
	    catch (ClassNotFoundException e) {
	    	e.printStackTrace();
	    }
	    catch (InstantiationException e) {
	    	e.printStackTrace();
	    }
	    catch (IllegalAccessException e) {
	    	e.printStackTrace();
	    }
	    
	    EasyClearCaseExplorer ecce = new EasyClearCaseExplorer();
		ecce.jfrMain.setVisible(true);

	}
	
	
	/**
	 * Logger that displays the logs into a JList.
	 * @see ILogDevice
	 * */
	class EasyClearCaseExplorerLog implements ILogDevice{

		private boolean errorHasOcurred = false;
		private StringBuilder log = new StringBuilder();
		private StringBuilder errorLog = new StringBuilder();
		
		public void error(String msg, Exception e) {
			DefaultListModel dm = (DefaultListModel)lstLog.getModel();
			String logmsg = "<html><font color='red'>["+sdf.format(new Date())+"] "+msg+"</font></html>";
			log.append(logmsg).append("\n");
			dm.insertElementAt(logmsg, 0);
			e.printStackTrace();
			errorHasOcurred = true;
		}

		public void log(String log) {
			DefaultListModel dm = (DefaultListModel)lstLog.getModel();
			if(log.toLowerCase().contains("executing command")){
				log = "<html><font color='blue'>["+sdf.format(new Date())+"] "+log+"</font></html>";
			}
			else if(log.toLowerCase().contains("error")){
				log = "<html><font color='red'>["+sdf.format(new Date())+"] "+log+"</font></html>";
				errorHasOcurred = true;
			}
			else if(log.toLowerCase().contains("warning")){
				log = "<html><font color='#c6a300'>["+sdf.format(new Date())+"] "+log+"</font></html>";
				errorHasOcurred = true;
			}
			else{
				log = "["+sdf.format(new Date())+"] " + log;
			}
			errorLog.append(log).append("\n");
			dm.insertElementAt(log, 0);
		}
		
		@Override
		public boolean errorHasOcurred(){
			return errorHasOcurred;
		}

		@Override
		public void lookForErrorsAgain() {
			errorHasOcurred = false;
		}
		
		public String toString(){
			return log.toString() + "\n"+errorLog.toString();
		}
	}
	

	/**
	 * Status listener to update the "Task status panel" when a task status has changed.
	 * */
	class TaskStatusDisplayListener extends TaskStatusListener{
		@Override
		public void statusChanged(TaskStatus oldValue, TaskStatus newValue) {
			lstTasks.repaint();
			lstTasks.validate();
		}
	}
	
	
	/**
	 * Customized renderer to render the tasks in the JList.
	 * */
	class TaskCellRenderer extends JLabel implements ListCellRenderer{
		private static final long serialVersionUID = -5919737760866662491L;
		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
			Task task = (Task)value;
			String startTime = "waiting...";
			if(task.getTaskStartTime()!=null){
				startTime = sdf.format(task.getTaskStartTime());
			}
			else if(task.getStatus() == TaskStatus.CANCELLED){
				startTime = "cancelled";
			}
			setText("["+startTime+"] "+task.getName()+" "+task.getStatus().name());
			return this;
		}
	}
	
	
	/**
	 * TaskGroup that will display the selected folder in the tree component into the main window
	 * after all child tasks are done.
	 * */
	class UpdateStatusAfterClearCaseOperationsTaskGroup extends TaskGroup{
		private EasyClearCaseFile file;
		private boolean generateReport = false;

		public UpdateStatusAfterClearCaseOperationsTaskGroup(EasyClearCaseFile file){
			this.file = file;
			lastUpdateStatusTask = this;
		}
		
		public void run(){
			if(lastUpdateStatusTask == this){
				changeStatus(TaskStatus.IN_PROGRESS);
				displayFiles(file);
				changeStatus(TaskStatus.COMPLETE_SUCCESS);
			}
			else {
				changeStatus(TaskStatus.CANCELLED);
			}
			if(generateReport){
				generateReport(tasks);
			}
		}

		@Override
		public void startTasks() {
			for (Task task : tasks) {
				executeTask(task, true);
			}
		}

		@Override
		protected void groupReadyToStart() {
			executeTask(this,true);
		}
		
		
		public void setGenerateReport(boolean generate){
			this.generateReport = generate;
		}
	}
	
	
	/**
	 * TaskGroup that will display the selected folder in the tree component into the main window
	 * after all child tasks are done.
	 * Child tasks are executed sequentially.
	 * */
	class UpdateStatusAfterClearCaseOperationsSequencedTaskGroup extends UpdateStatusAfterClearCaseOperationsTaskGroup{
		public UpdateStatusAfterClearCaseOperationsSequencedTaskGroup(EasyClearCaseFile file) {
			super(file);
		}

		@Override
		public void startTasks() {
			for(int i=0; i<tasks.size();i++){
				if(i!=tasks.size()-1){
					final int nextTask = i+1;
					tasks.get(i).addTaskStatusListener(new TaskCompletedStatusListener(){
						@Override
						public void doAfterTask(TaskStatus e) {
							if(!cancelled){
								Task followingTask = tasks.get(nextTask);
								System.out.println("Doing task number "+nextTask+" ==> "+followingTask.toString());
								executeTask(followingTask,true);
							}
						}
					});
				}
			}
			executeTask(tasks.get(0),true);
		}
	};
	
	
	/**
	 * Taskgroup that will update the selected folder in the tree status after all child tasks are completed.
	 * */
	class UpdateTreeStatusAfterClearCaseOperationsTaskGroup extends TaskGroup{
		private EasyClearCaseFile file;
		private boolean displayFilesAfter = false;
		public UpdateTreeStatusAfterClearCaseOperationsTaskGroup(EasyClearCaseFile file){
			this.file = file;
		}
		
		public void displayFilesAfter(boolean display){
			this.displayFilesAfter = display;
		}
		
		public void run(){
			changeStatus(TaskStatus.IN_PROGRESS);
			try {
				ClearCaseTask cct = new ClearCaseTask(new ListFiles(file.getFile().getParentFile()));
				final MemoryLog memoryLog = new MemoryLog();
				cct.setOutputLogger(memoryLog);
				cct.addTaskStatusListener(new TaskCompletedStatusListener(){
					@Override
					public void doAfterTask(TaskStatus e) {
						HashMap<String,ClearCaseStatus> mapStatus = createStatusMap(memoryLog);
						DefaultMutableTreeNode node = searchTree(file);
						DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)node.getParent();
						updateDirectoryTree(parentNode, mapStatus);	
						if(displayFilesAfter){
							displayFiles(file);
						}
					}
				});
				cct.setName("Update directories' status");
				executeTask(cct, true);
				changeStatus(TaskStatus.COMPLETE_SUCCESS);
			}
			catch (Exception e) {
				generalLog.error("Error listing directory files.", e);
				changeStatus(TaskStatus.COMPLETE_FAIL);
			}
			
		}

		private DefaultMutableTreeNode searchTree(EasyClearCaseFile file) {
			DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getModel().getRoot();
			EasyClearCaseFile nodeFile;
			nodeFile = (EasyClearCaseFile)node.getUserObject();
			if(nodeFile.equals(file)){
				return node;
			}
			else{
				DefaultMutableTreeNode treeNode = searchTree(node,file);
				return treeNode;
			}
		}
		
		

		private DefaultMutableTreeNode searchTree(DefaultMutableTreeNode node, EasyClearCaseFile file2) {
			EasyClearCaseFile ecf = (EasyClearCaseFile) node.getUserObject();
			if(ecf.equals(file2)){
				return node;
			}
			else{
				int childs = node.getChildCount();
				DefaultMutableTreeNode foundNode = null;
				for(int i=0; i<childs;i++){
					foundNode = searchTree((DefaultMutableTreeNode)node.getChildAt(i), file2);
					if(foundNode!=null)return foundNode;
				}
			}
			return null;
		}

		@Override
		public void startTasks() {
			for(Task task:tasks){
				executeTask(task,true);
			}
		}

		@Override
		protected void groupReadyToStart() {
			executeTask(this,true);
		}
	}
	
	
	/**
	 * No use.
	 * */
	class SimpleTaskGroup extends TaskGroup{
		@Override
		public void startTasks() {
			int size = tasks.size();
			if(size>0){
				Task lastTask = tasks.get(size-1);
				lastTask.addTaskStatusListener(new TaskCompletedStatusListener(){
					@Override
					public void doAfterTask(TaskStatus e) {
						changeStatus(TaskStatus.COMPLETE_SUCCESS);
					}
				});
			}
			changeStatus(TaskStatus.IN_PROGRESS);
			for(Task t:this.tasks){
				executeTask(t, true);
			}
		}

		@Override
		public void run() {
			startTasks();
		}
	}
	
	
	
	/**
	 * No use yet. Soon it will be changed to MultipleFileDescribe. Then it will be useful.
	 * */
	class SingleFileDescribeTask extends ClearCaseTask{
		public SingleFileDescribeTask(EasyClearCaseFile file,final Object[]rowdata) {
			super(new ParametrizedCommand("cleartool describe -short ${filepath}"));
			((ParametrizedCommand)this.clearToolCommand).setParameter("filepath", file.getFile().getAbsolutePath());
			final MemoryLog output = new MemoryLog();
			setOutputLogger(output);
			addTaskStatusListener(new TaskCompletedStatusListener(){
				@Override
				public void doAfterTask(TaskStatus e) {
					if(e == TaskStatus.COMPLETE_SUCCESS){
						String line = output.toString().trim();
						ClearCaseStatus ccstatus = parseStatusFromShortDescription(line);
						rowdata[0] = ccstatus;
						EasyClearCaseFile easyfile = (EasyClearCaseFile) rowdata[easyfileIndexInTable];
						easyfile.setStatus(ccstatus);
						tableFiles.repaint();
						tableFiles.validate();
					}
				}
			});
		}
	}
	
	
	


	
	/**
	 * Returns the filename from a cleartool output line.
	 * */
	private String parseFileNameFormShortDescription(String line){
		int index = line.indexOf("@@");
		if(index<0){
			return line;
		}
		else{
			return line.substring(0,index);
		}
	}
	
	
	/**
	 * Returns the status from a cleartool output line.
	 * */
	private ClearCaseStatus parseStatusFromShortDescription(String line){
		int index = line.indexOf("@@");
		if(index<0){
			return ClearCaseStatus.PRIVATE_FILE;
		}
		else{
			if(line.endsWith("CHECKEDOUT")){
				return ClearCaseStatus.CHECKEDOUT;
			}
			else{
				return ClearCaseStatus.FILE_ELEMENT;
			}
		}
	}
	
	
	/**
	 * Update status on the directory tree.
	 * */
	private void updateDirectoryTree(final DefaultMutableTreeNode node, HashMap<String, ClearCaseStatus> mapStatus) {
		EasyClearCaseFile easyFile = (EasyClearCaseFile)node.getUserObject();
		ClearCaseStatus clearCaseStatus = mapStatus.get(easyFile.getName());
		easyFile.setStatus(clearCaseStatus);
		int childs = node.getChildCount();
		EasyClearCaseFile childFolder;
		for(int i=0;i<childs;i++){
			DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node.getChildAt(i);
			childFolder = (EasyClearCaseFile)childNode.getUserObject();
			childFolder.setStatus(mapStatus.get(childFolder.getName()));
			if(childNode.getChildCount()>0){
				updateDirectoryTree(childNode, mapStatus);
			}
		}
		tree.repaint();
		tree.validate();
	}
	
	
	/**
	 * Base class to execute clearcase tasks when mouse is released (to use with the files).
	 * */
	abstract class ClearCaseMouseAdapter extends MouseAdapter{
		private String taskgroupname;
		public ClearCaseMouseAdapter(String taskgroupName){
			this.taskgroupname = taskgroupName;
		}
		@Override
		public void mouseReleased(MouseEvent e) {
			Comment commonComment = new Comment();
			int rows[] = tableFiles.getSelectedRows();
			DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
			EasyClearCaseFile easyParentfile = (EasyClearCaseFile)node.getUserObject();
			UpdateStatusAfterClearCaseOperationsSequencedTaskGroup tgroup = new UpdateStatusAfterClearCaseOperationsSequencedTaskGroup(easyParentfile);
			int modelRow;
			for(int row: rows){
				RowSorter<?> rowsorter = tableFiles.getRowSorter();
				modelRow = rowsorter.convertRowIndexToModel(row);
				EasyClearCaseFile easyfile = (EasyClearCaseFile)tableFiles.getModel().getValueAt(modelRow, easyfileIndexInTable);
				if(easyfile!=null){
					doClearCaseTask(easyfile, tgroup, commonComment);
				}
			}
			tgroup.setName(taskgroupname);
			tgroup.startTasks();
		}
		
		public abstract void doClearCaseTask(EasyClearCaseFile file, TaskGroup tgroup, Comment commonComment);
	}

	
	/**
	 * Base class that when mouse is released retrives the selected folder in the tree and then "processFile". 
	 * */
	abstract class EasyFileMouseAdapter extends MouseAdapter {
		
		@Override
		public void mouseReleased(MouseEvent e) {
			DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
			if (node != null) {
				EasyClearCaseFile easyfile = (EasyClearCaseFile) node.getUserObject();
				processFile(easyfile);
			}
		}
		


		public abstract void processFile(EasyClearCaseFile easyfile);
	}
	
}
